package monitor

import (
	"context"
	"fmt"
	"gitee.com/aldrice/monitor/api/common"
	. "gitee.com/aldrice/monitor/configs"
	"gitee.com/aldrice/monitor/ent"
	"gitee.com/aldrice/monitor/ent/object"
	"gitee.com/aldrice/monitor/ent/setting"
	"gitee.com/aldrice/monitor/ent/user"
	. "gitee.com/aldrice/monitor/internal/monitor/definition"
	"gitee.com/aldrice/monitor/internal/router"
	"strconv"
	"time"
)

type addObjectReq struct {
	Token string `json:"token" validate:"required"`
}

// AddObject
// @Summary Add Exist Object
// @Description 通过Token添加已有的服务器
// @ID monitor-object-add
// @Tags object
// @Accept json
// @Produce json
// @Route /monitor/object/token [post]
func AddObject(c *router.Context) error {
	req := new(addObjectReq)
	if err := c.Bind(req); err != nil {
		return err
	}
	if err := c.Validate(req); err != nil {
		return err
	}

	uid, ctx := c.UID(), c.Ctx()
	oid, err := c.DB().Object.Query().Where(object.TokenEqualFold(req.Token)).FirstID(ctx)
	if err != nil {
		return err
	}
	_, err = c.DB().Object.UpdateOneID(oid).AddOwnedByIDs(uid).Save(ctx)
	if err != nil {
		return err
	}
	return c.ApiSuccess(oid)
}

type signUpObjectReq struct {
	Nickname      string `json:"nickname" validate:"required"`
	PublicAddress string `json:"public_address"`
	LocalAddress  string `json:"local_address"`
}

// SignUpObject
// @Summary Object Create
// @Description 用户登记其名下的机器
// @ID monitor-object-create
// @Tags object
// @Accept json
// @Produce json
// @Route /monitor/object [post]
func SignUpObject(c *router.Context) error {
	req := new(signUpObjectReq)
	if err := c.Bind(req); err != nil {
		return err
	}
	if err := c.Validate(req); err != nil {
		return err
	}

	var o *ent.Object
	err := c.Tx(func(tx *ent.Tx, ctx context.Context) error {
		oCreate := tx.Object.Create().
			SetNickname(req.Nickname).
			AddOwnedByIDs(c.UID())
		if req.PublicAddress != "" {
			oCreate.SetPublicAddress(req.PublicAddress)
		}
		if req.LocalAddress != "" {
			oCreate.SetLocalAddress(req.LocalAddress)
		}

		var err error
		o, err = oCreate.Save(ctx)
		if err != nil {
			return err
		}

		// set up default setting
		if err := setupDefaultSetting(ctx, tx, o); err != nil {
			return err
		}

		// generate jwt for this object
		sign, err := common.CreateSign(o.ID, 0, ObjectSecKey, JwtObjectExpiredTime)
		if err != nil {
			return err
		}

		_, err = tx.Object.UpdateOne(o).SetToken(sign).Save(ctx)
		return err
	})
	if err != nil {
		return err
	}
	return c.ApiSuccess(o)
}

type editObjectReq struct {
	Nickname       string `json:"nickname" validate:"required"`
	ReportInterval int    `json:"report_interval" validate:"required"`
	ResendTimes    int    `json:"resend_times" validate:"required"`
	FetchTimeout   int    `json:"fetch_timeout" validate:"required"`

	ID int `json:"id" validate:"required"`
}

// EditObject
// @Summary Edit Object
// @Description 用户修改其名下的机器信息
// @ID monitor-object-edit
// @Tags object
// @Accept json
// @Produce json
// @Route /monitor/object [put]
func EditObject(c *router.Context) error {
	req := new(editObjectReq)
	if err := c.Bind(req); err != nil {
		return err
	}
	if err := c.Validate(req); err != nil {
		return err
	}

	_, err := c.DB().Object.Update().Where(object.IDEQ(req.ID)).
		SetNickname(req.Nickname).
		SetReportInterval(req.ReportInterval).
		SetResendTimes(req.ResendTimes).
		SetFetchTimeout(req.FetchTimeout).
		Save(c.Ctx())
	if err != nil {
		return err
	}

	return c.ApiSuccess(nil)
}

// GetObjects
// @Summary Get Objects
// @Description 获取用户拥有的所有机器
// @ID monitor-object-gets
// @Tags object
// @Accept param
// @Produce json
// @Route /monitor/objects [get]
func GetObjects(c *router.Context) error {
	db, ctx := c.DB(), c.Ctx()
	oQry := db.Object.Query().Where(object.HasOwnedByWith(user.IDEQ(c.UID())))

	if c.QueryParam("nickname") != "" {
		oQry.Where(object.NicknameContainsFold(c.QueryParam("nickname")))
	}
	if c.QueryParam("id") != "" {
		oid, err := strconv.Atoi(c.QueryParam("id"))
		if err != nil {
			return err
		}
		oQry.Where(object.IDEQ(oid))
	}
	if c.QueryParam("public_address") != "" {
		oQry.Where(object.PublicAddressContainsFold(c.QueryParam("public_address")))
	}
	if c.QueryParam("local_address") != "" {
		oQry.Where(object.PublicAddressContainsFold(c.QueryParam("local_address")))
	}

	cnt, err := oQry.Clone().Count(ctx)
	if err != nil {
		return err
	}
	if cnt == 0 {
		return c.StdTable(0, nil)
	}

	page, limit := c.DeterminePage()
	ord := c.DetermineOrder(object.Columns)
	objects, err := oQry.Limit(limit).Offset(page).Order(ord).All(ctx)
	if err != nil {
		return err
	}
	return c.StdTable(cnt, objects)
}

type getObjectRsp struct {
	ent.Object
	IsOnline string `json:"is_online"`
}

// GetObject
// @Summary Get Object
// @Description 获取用户指定服务器的详细信息
// @ID monitor-object-get
// @Tags object
// @Accept param
// @Produce json
// @Route /monitor/object [get]
func GetObject(c *router.Context) error {
	db, ctx := c.DB(), c.Ctx()
	oid, err := strconv.Atoi(c.Param("id"))
	if err != nil {
		return err
	}

	obj, err := db.Object.Query().Where(object.IDEQ(oid)).First(ctx)
	if err != nil {
		return err
	}

	isOnline := fmt.Sprintf("%t", time.Now().Before(obj.LastUpdatedAt.Add(time.Duration(obj.ReportInterval)*time.Second*2)))
	return c.ApiSuccess(getObjectRsp{
		Object:   *obj,
		IsOnline: isOnline,
	})
}

func setupDefaultSetting(ctx context.Context, db *ent.Tx, obj *ent.Object) error {
	ms, err := db.Metric.Query().All(ctx)
	if err != nil {
		return err
	}

	sCreates := make([]*ent.SettingCreate, len(ms))

	for i, m := range ms {
		sCreate := db.Setting.Create().
			SetLength(DefaultLength).
			SetWithObject(obj).
			SetNickname(fmt.Sprintf("%d-%s", obj.ID, m.Name)).
			SetMethod(setting.MethodNone).
			SetWithMetric(m)
		if m.Name != MetricCPURate && m.Name != MetricMemRate {
			sCreate.SetEnable(false)
		}
		sCreates[i] = sCreate
	}

	_, err = db.Setting.CreateBulk(sCreates...).Save(ctx)
	return err
}
